-
Assembly Calling Conventions explanation and visualization in IDA .
-
It was only talked about
_cdecl
-
cdecl (C/C++ calling convention)
-
.
-
-
cdecl only allows for 1 return value?
-
The cdecl calling convention does not inherently limit you to one return value.
-
The apparent “single return value” rule comes from C language semantics and typical ABI usage, not from cdecl itself .
-
In C:
-
A function syntactically returns one expression.
-
Classic tutorials emphasize EAX.
-
-
But ABI-wise, that one expression can be:
-
a struct
-
a vector
-
written via hidden pointer
-
split across registers
-
-
Specification
-
Under classic 32-bit
cdecl:-
Integer/pointer return → EAX
-
Floating return → ST0 (x87)
-
Small aggregates → sometimes registers
-
Larger aggregates → hidden pointer (sret)
-
-
The convention defines how values are returned, not that only one logical value is allowed.
sysv (System V AMD64 ABI)
-
Aggregates (structs/unions) ≤ 16 bytes may be passed in registers (split into up to two 8-byte chunks).
-
Aggregates > 16 bytes are passed in memory.
-
Practically, this often behaves like pass-by-reference at the ABI boundary (because the object lives in memory), but technically it is still pass-by-value via memory.
win64 (Windows x64 calling convention)
-
Commonly referred to as:
-
win64 -
ms_abi(LLVM/Clang attribute)
-
-
Aggregates > 8 bytes are passed by reference (hidden pointer).
-
Only 1, 2, 4, or 8-byte values are passed directly in registers.
Odin's "contextless"
-
It passes all parameters larger than 16 bytes by reference.
-
(2026-02-23)
-
"contextless"vs"c"-
Barinzaya:
-
Odin will implicitly pass large things by-pointer (since you can't modify proc arguments anyway), whereas C may not (because you can modify arguments in C).
-
In some cases, they may end up the same, but aren't in all cases.
-
It's still technically using the C ABI, it's mostly just passing pointers instead of values and inserting some implicit parameters (for context if applicable, and multiple return values). Different semantics, different ideals
-
So they're not compatible because things are different, but internally it's still just the C ABI with some changes to the parameters
-
-
avanspector:
-
registers are the same, the procedure prelude routine is not exactly the same
-
registers have to be the same in order to be compatible with native debuggers
-
-
-
"contextless"vs"win64"-
Caio:
-
"So it's more similar to how "win64" does it, then? but passing as a reference if above 16 bytes instead of 8 bytes?"
-
-
Barinzaya:
-
AFAIK they're still not quite the same, Win64 CC will make a copy on the stack and then pass a pointer to that, Odin CC will pass a pointer directly to the original
-
avanspector:
-
IIRC bill made a change some time ago to follow win64 cc in this case.
-
-
-
-
avanspector:
-
Odin's abi is not set in stone yet so I wouldn't rely on it long term.
-
-
rats:
-
i think most modern languages do something similar to odin
-
special internal calling convention unless you specify otherwise
-
zig has that
@callconv(.c)thing, as well as automatically applying it to all export functions i think -
rust has
#[repr(c)]or whatever
-
stdcall
-
stdcall is a legacy 32-bit x86 calling convention primarily used on Windows. It standardizes stack cleanup and was widely used by the Win32 API.
-
Typical behavior:
-
Arguments passed on the stack (right to left)
-
Callee cleans the stack
-
Function names often decorated (e.g., _Func@8)
-
Return value in EAX (for integer/pointer)
-
This differs from cdecl, where the caller cleans the stack.
-
-
On x86-64
-
stdcall is effectively obsolete.
-
-
Reason: both major 64-bit ABIs use register-based calling and ignore legacy modifiers:
-
System V AMD64 ABI
-
Microsoft x64 calling convention
-
-
On 64-bit Windows (MSVC):
-
__stdcallis accepted but ignored -
All functions use the unified Microsoft x64 ABI
-
-
When stdcall still matters
-
Use it only when targeting 32-bit Windows and you must match an existing binary interface.
-
Common cases:
-
Win32 API functions
-
Old DLL exports
-
Legacy COM interfaces
-
Reverse engineering 32-bit binaries
-
-
-
Performance:
-
Historically:
-
Slightly smaller call sites than cdecl
-
Reduced stack adjustment instructions
-
-
In practice today:
-
Differences are negligible
-
Inlining and register ABIs dominate performance
-
Irrelevant on x86-64 and ARM64
-
-
fastcall
-
fastcall is a historical x86 (32-bit) calling convention designed to reduce call overhead by passing some arguments in registers instead of on the stack.
-
What fastcall means
-
Typical characteristics (32-bit x86):
-
First few arguments passed in registers (commonly ECX, EDX)
-
Remaining arguments on the stack
-
Callee usually cleans the stack
-
Intended to reduce memory traffic vs cdecl/stdcall
-
-
On x86-64
-
fastcall is essentially obsolete.
-
-
Reason: both major 64-bit ABIs already pass arguments in registers:
-
System V AMD64 ABI
-
Microsoft x64 calling convention
-
-
They are effectively “fastcall-like” by default, but standardized.
-
__fastcallis ignored or treated as default on x64 MSVC -
Most modern languages do not expose it on 64-bit targets
-
When fastcall still matters
-
Use it only if:
-
You are targeting 32-bit x86
-
You must match an existing binary interface
-
You are working with legacy Windows code
-
-
Common legacy cases:
-
Win32 APIs (some internal ones)
-
Old game engines
-
Reverse engineering
-
Binary hooking on x86
-
-
-
Performance
-
On modern systems:
-
Difference vs default x64 ABI: none
-
Difference vs optimized code with inlining: usually negligible
-
-
Biggest wins come from:
-
inlining
-
data layout
-
reducing call frequency
-
-